#include "scheduler.h"

int FCFSScheduler::create(int ppid, const std::vector<Command> &commands, int pc, PCB::Priority priority)
{
    int pid = -1;
    for (int i = 1; i < 65536; i++)
    {
        if (PCB_table.find(i) == PCB_table.end())
        {
            pid = i;
            break;
        }
    }
    if (pid == -1)
        return -1;

    auto PCB_ptr = std::make_shared<PCB>(PCB{
        ppid,
        pid,
        PCB::NEW,
        pc,
        commands,
        PCB::Priority{
            0, 0, 0, 0}});

    PCB_table.insert({pid, PCB_ptr});
    new_queue.push_back(pid);

    return pid;
}

int FCFSScheduler::admit(int pid)
{
    auto it = new_queue.begin();
    bool found = false;

    for (; it != new_queue.end(); it++)
    {
        if (*it == pid)
        {
            found = true;
            break;
        }
    }

    if (!found)
    {
        return -1;
    }

    ready_queue.push_back(*it);
    new_queue.erase(it);
    return 0;
}

int FCFSScheduler::get_task()
{
    while (!new_queue.empty() && (max_progress_count <= 0 || ready_queue.size() < max_progress_count))
    {
        int pid = new_queue.front();
        ready_queue.push_back(pid);
        new_queue.pop_front();
    }

    if (ready_queue.size() == 0)
        return -1;

    return ready_queue.front();
}

int FCFSScheduler::set_run(int pid)
{
    auto it = ready_queue.begin();
    bool found = false;

    for (; it != ready_queue.end(); it++)
    {
        if (*it == pid)
        {
            found = true;
            break;
        }
    }

    if (!found)
    {
        return -1;
    }

    ready_queue.erase(it);

    PCB_table[pid]->state = PCB::RUN;
    run_queue.push_back(pid);

    return 0;
}

int FCFSScheduler::set_ready(int pid)
{
    auto it = wait_queue.begin();
    bool found = false;

    for (; it != wait_queue.end(); it++)
    {
        if (*it == pid)
        {
            found = true;
            break;
        }
    }

    if (found)
    {
        ready_queue.erase(it);
        PCB_table[pid]->state = PCB::READY;
        ready_queue.push_back(pid);
        return 0;
    }

    it = run_queue.begin();
    found = false;

    for (; it != run_queue.end(); it++)
    {
        if (*it == pid)
        {
            found = true;
            break;
        }
    }

    if (found)
    {
        run_queue.erase(it);
        PCB_table[pid]->state = PCB::READY;
        ready_queue.push_back(pid);
        return 0;
    }

    return -1;
}

int FCFSScheduler::set_wait(int pid)
{
    auto it = run_queue.begin();
    bool found = false;

    for (; it != run_queue.end(); it++)
    {
        if (*it == pid)
        {
            found = true;
            break;
        }
    }

    if (!found)
    {
        return -1;
    }

    run_queue.erase(it);

    PCB_table[pid]->state = PCB::WAIT;
    run_queue.push_back(pid);

    return 0;
}

int FCFSScheduler::exit(int pid)
{
    return kill(pid);
}

int FCFSScheduler::kill(int pid)
{
    PCB_table.erase(pid);

    for (auto it = new_queue.begin(); it != new_queue.end(); it++)
    {
        if (*it == pid)
        {
            new_queue.erase(it);
            return 0;
        }
    }
    for (auto it = ready_queue.begin(); it != ready_queue.end(); it++)
    {
        if (*it == pid)
        {
            ready_queue.erase(it);
            return 0;
        }
    }
    for (auto it = run_queue.begin(); it != run_queue.end(); it++)
    {
        if (*it == pid)
        {
            run_queue.erase(it);
            return 0;
        }
    }
    for (auto it = wait_queue.begin(); it != wait_queue.end(); it++)
    {
        if (*it == pid)
        {
            wait_queue.erase(it);
            return 0;
        }
    }

    return 0;
}

std::shared_ptr<PCB> FCFSScheduler::get_PCB(int pid)
{
    decltype(PCB_table)::iterator it;
    if ((it = PCB_table.find(pid)) != PCB_table.end())
    {
        return it->second;
    }
    else
    {
        return nullptr;
    }
}

#ifdef UNIT_TEST

#include <gtest/gtest.h>

TEST(SCHEDULER_TEST, FCFS_TEST)
{
    Scheduler *s = new FCFSScheduler();

    int pid[4];

    for (int i = 0; i < 4; i++)
        pid[i] = s->create(0, {}, 0, {0, 0, 0, 0});

    for (int i = 0; i < 4; i++)
    {
        EXPECT_EQ(pid[i], s->get_task());
        s->set_run(pid[i]);
    }
}

#endif
